/******************************************************************/
/*                                                                */
/*  Winpooch : Windows Watchdog                                   */
/*  Copyright (C) 2004-2005  Benoit Blanchon                      */
/*                                                                */
/*  This program is free software; you can redistribute it        */
/*  and/or modify it under the terms of the GNU General Public    */
/*  License as published by the Free Software Foundation; either  */
/*  version 2 of the License, or (at your option) any later       */
/*  version.                                                      */
/*                                                                */
/*  This program is distributed in the hope that it will be       */
/*  useful, but WITHOUT ANY WARRANTY; without even the implied    */
/*  warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR       */
/*  PURPOSE.  See the GNU General Public License for more         */
/*  details.                                                      */
/*                                                                */
/*  You should have received a copy of the GNU General Public     */
/*  License along with this program; if not, write to the Free    */
/*  Software Foundation, Inc.,                                    */
/*  675 Mass Ave, Cambridge, MA 02139, USA.                       */
/*                                                                */
/******************************************************************/


/******************************************************************/
/* Includes                                                       */
/******************************************************************/

// module's interface
#include "ProcListWnd.h"

// standard headers
#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <tchar.h>

// project's headers
#include "Assert.h"
#include "Language.h"
#include "ProcList.h"
#include "Resources.h"
#include "SpyServer.h"
#include "Trace.h"


/******************************************************************/
/* Internal constants                                             */
/******************************************************************/

// column identifiers
#define COL_NAME	0
#define COL_PID		1
#define COL_STATE	2
#define COL_PATH	3

// window class name
#define WC_PROCWND TEXT("ProcListWnd")

LPCTSTR g_szProcessMenu = TEXT("Process menu") ;
LPCTSTR g_szHookProcess = TEXT("Hook selected process") ;
LPCTSTR g_szUnhookProcess = TEXT("Unhook selected process") ;
LPCTSTR g_szKillProcess = TEXT("Kill selected process") ;


/******************************************************************/
/* Internal functions                                             */
/******************************************************************/

LRESULT CALLBACK _ProcListWnd_WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) ;

BOOL _ProcListWnd_EnumCallback (void *, PROCSTRUCT *) ;


/******************************************************************/
/* Exported function : RegisterClass                              */
/******************************************************************/

BOOL ProcListWnd_RegisterClass (HINSTANCE hInstance) 
{
  WNDCLASS wndclass ;

  wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
  wndclass.lpfnWndProc   = _ProcListWnd_WndProc ;
  wndclass.cbClsExtra    = 0 ;
  wndclass.cbWndExtra    = 0 ;
  wndclass.hInstance     = hInstance ;
  wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;
  wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
  wndclass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1) ;
  wndclass.lpszMenuName  = NULL ;
  wndclass.lpszClassName = WC_PROCWND ;

  return 0!=RegisterClass (&wndclass) ;
}


/******************************************************************/
/* Exported function : CreateWindow                               */
/******************************************************************/

HWND ProcListWnd_CreateWindow (HINSTANCE hInstance, HWND hwndParent)
{  
  return  CreateWindow (WC_PROCWND, NULL,
			WS_CHILD,
			CW_USEDEFAULT, CW_USEDEFAULT,
			CW_USEDEFAULT, CW_USEDEFAULT, 
			hwndParent, NULL, hInstance, NULL) ;
}


/******************************************************************/
/* Internal function : WndProc                                    */
/******************************************************************/

LRESULT CALLBACK _ProcListWnd_WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
  static HWND		g_hwndList ;
  static HINSTANCE	g_hInstance ;
  static HMENU		g_hmenuProcess ;
  static DWORD		g_nSelectedProcessId ;

  int		nWidth ;
  int		nHeight ;
  LV_COLUMN	lvc ;


  switch (message)
    {
    case WM_CREATE:
      
      g_hInstance = ((CREATESTRUCT*)lParam)->hInstance ;

      g_hwndList = CreateWindowEx (WS_EX_CLIENTEDGE, WC_LISTVIEW, NULL,
				  WS_CHILD|WS_VISIBLE|WS_VSCROLL|LVS_REPORT|LVS_SINGLESEL,
				   0,0,0,0, hwnd, (HMENU)IDC_PROCESSLIST, g_hInstance, NULL) ;
      ListView_SetExtendedListViewStyle (g_hwndList, LVS_EX_FULLROWSELECT) ;

      g_nSelectedProcessId = 0 ;

      // menu will be created by WM_LANGUAGECHANGED
      g_hmenuProcess = NULL ;
    
      // clear LVC struct
      ZeroMemory (&lvc, sizeof(lvc)) ;
      lvc.mask = LVCF_TEXT|LVCF_WIDTH|LVCF_SUBITEM ;
      lvc.pszText = TEXT("") ;
      
      // add process name column
      lvc.cx		= 100 ;
      lvc.iSubItem	= COL_NAME ;
      ListView_InsertColumn (g_hwndList, lvc.iSubItem, &lvc) ; 
      
      // add PID column
      lvc.cx		= 40 ;
      lvc.iSubItem	= COL_PID ;
      ListView_InsertColumn (g_hwndList, lvc.iSubItem, &lvc) ; 

      // add state column
      lvc.cx		= 150 ;
      lvc.iSubItem	= COL_STATE ;
      ListView_InsertColumn (g_hwndList, lvc.iSubItem, &lvc) ; 
      
      // add path
      lvc.cx		= 500 ;
      lvc.iSubItem	= COL_PATH ;
      ListView_InsertColumn (g_hwndList, lvc.iSubItem, &lvc) ; 

      SendMessage (hwnd, WM_LANGUAGECHANGED, 0, 0) ;
	
      return 0 ; // case WM_CREATE:

    case WM_LANGUAGECHANGED:

      // clear LVC struct
      ZeroMemory (&lvc, sizeof(lvc)) ;
      lvc.mask = LVCF_TEXT ;
      
      // add process name column
      lvc.pszText = (LPTSTR) STR_DEF (_PROCESS, TEXT("Process")) ;
      ListView_SetColumn (g_hwndList, COL_NAME, &lvc) ; 
      
      // add PID column
      lvc.pszText = (LPTSTR) STR_DEF (_PID, TEXT("PID")) ;
      ListView_SetColumn (g_hwndList, COL_PID, &lvc) ; 

      // add state column
      lvc.pszText = (LPTSTR) STR_DEF (_STATE, TEXT("State")) ;
      ListView_SetColumn (g_hwndList, COL_STATE, &lvc) ; 
      
      // add path
      lvc.pszText = (LPTSTR) STR_DEF (_PATH, TEXT("Path")) ;
      ListView_SetColumn (g_hwndList, COL_PATH, &lvc) ; 

      // create processes menu
      DestroyMenu (g_hmenuProcess) ;
      g_hmenuProcess = CreatePopupMenu () ;
      AppendMenu (g_hmenuProcess, MF_STRING|MF_GRAYED, 0, 
		  STR_DEF(_PROCESS_MENU,g_szProcessMenu)) ;
      AppendMenu (g_hmenuProcess, MF_SEPARATOR, 0, NULL) ;
      AppendMenu (g_hmenuProcess, MF_STRING, IDM_PROCESS_HOOK,
		  STR_DEF(_HOOK_SELECTED_PROCESS,g_szHookProcess)) ;
      AppendMenu (g_hmenuProcess, MF_STRING, IDM_PROCESS_UNHOOK, 
		  STR_DEF(_UNHOOK_SELECTED_PROCESS,g_szUnhookProcess)) ;
      AppendMenu (g_hmenuProcess, MF_STRING, IDM_PROCESS_KILL, 
		  STR_DEF(_KILL_SELECTED_PROCESS,g_szKillProcess)) ;
      
      PostMessage (hwnd, WM_PROCLISTCHANGED, 0, 0) ;

      return 0 ;
      
    case WM_DESTROY:

      return 0 ;

    case WM_SIZE:
      nWidth = LOWORD (lParam) ;
      nHeight = HIWORD (lParam) ;
      MoveWindow (g_hwndList, 0, 0, nWidth, nHeight, TRUE) ;

      return 0 ;

    case WM_PROCLISTCHANGED: // ======== Process list changed ========
      {
	LVFINDINFO	lvfi ;
	int		iItem ;

	ListView_DeleteAllItems (g_hwndList) ;
	ProcList_Enum (_ProcListWnd_EnumCallback, g_hwndList) ;

	if( g_nSelectedProcessId )
	  {
	    lvfi.flags = LVFI_PARAM ;
	    lvfi.lParam = g_nSelectedProcessId ;
	    
	    iItem = ListView_FindItem (g_hwndList, -1, &lvfi) ;
	    	    
	    if( iItem>=0 )
	      ListView_SetItemState (g_hwndList, iItem, LVIS_SELECTED, LVIS_SELECTED) ;
	  }
      }      
      return 0 ; // ======== Process list changed ========

    case WM_COMMAND:
      
      switch( LOWORD(wParam))
	{
	case IDM_PROCESS_HOOK: //  ======== 'Hook selected process' command ========
	  {
	    SpySrv_HookProcess (g_nSelectedProcessId) ;
	  }
	  return 0 ; //  ======== 'Hook selected process' command ========

	case IDM_PROCESS_UNHOOK: //  ======== 'Unhook selected process' command ========
	  {
	    SpySrv_UnhookProcess (g_nSelectedProcessId) ;
	  }
	  return 0 ; //  ======== 'Unhook selected process' command ========

	case IDM_PROCESS_KILL: //  ======== 'Kill selected process' command ========
	  {
	    DWORD nPid = g_nSelectedProcessId ;
	    g_nSelectedProcessId = 0 ;
	    if( nPid ) SpySrv_KillProcess (nPid) ;
	  }
	  return 0 ; //  ======== 'Kill selected process' command ========
	}

      break ; // case WM_COMMAND:

    case WM_NOTIFY: // ============ 'notify' message on 'processes' window ==============
      {
        union {    
	  NMHDR			*header ;
	  NMITEMACTIVATE	*itemactivate ;
	  NMLISTVIEW		*listview ; 
	  NMTTDISPINFO		*getdispinfo ; 
	} pnm ;

	pnm.header = (NMHDR*)lParam ;
	
	switch( pnm.header->idFrom )
	  {
	  case IDC_PROCESSLIST:
	    
	    switch( pnm.header->code )
	      {
	      case LVN_ITEMCHANGED: // ======== Item changed on 'processes' list-view ========
		{
		  // selection changed
		  if( pnm.listview->uNewState & LVIS_SELECTED )
		    {
		      // get pid of selected process
		      g_nSelectedProcessId = pnm.listview->lParam ;
		    }			  
		}			      
		return 0 ; //  ======== Item changed on 'processes' list-view ========
		
	      case NM_RCLICK: //  ======== Right click on 'processes' list-view ======== 
		{
		  BOOL		bEnable ;
		  BOOL		bIsCurProcess ;
		  POINT		pt ;
		  PROCSTRUCT	*pProc ;
		  
		  // get mouse position
		  GetCursorPos (&pt) ;

		  bIsCurProcess = g_nSelectedProcessId==GetCurrentProcessId() ;
		  
		  pProc = g_nSelectedProcessId ? ProcList_Get (g_nSelectedProcessId) : NULL ;
		  if( ! pProc ) g_nSelectedProcessId = 0 ;
		  
		  // enable/disable 'hook' menu item
		  bEnable = pProc && !ProcList_IsHooked(pProc) && !bIsCurProcess ;
		  EnableMenuItem (g_hmenuProcess, IDM_PROCESS_HOOK, 
				  MF_BYCOMMAND|(bEnable?MF_ENABLED:MF_GRAYED)) ;
		  
		  // enable/disable 'unhook' menu item
		  bEnable = pProc && ProcList_IsHooked(pProc) && !bIsCurProcess ;
		  EnableMenuItem (g_hmenuProcess, IDM_PROCESS_UNHOOK, 
				  MF_BYCOMMAND|(bEnable?MF_ENABLED:MF_GRAYED)) ;
		  
		  // enable/disable 'kill' menu item
		  bEnable = pProc!=NULL && ! bIsCurProcess ;
		  EnableMenuItem (g_hmenuProcess, IDM_PROCESS_KILL, 
				  MF_BYCOMMAND|(bEnable?MF_ENABLED:MF_GRAYED)) ;
		  
		  // display menu
		  TrackPopupMenu (g_hmenuProcess, TPM_RIGHTBUTTON, pt.x, pt.y, 0, hwnd, NULL) ;		
		}
		return 0 ; //  ======== Right click on 'processes' list-view ======== 	      
	      }
	  }
	}
         
      return 0 ; // ============ 'notify' message on 'processes' window ==============
    }

  return DefWindowProc (hwnd, message, wParam, lParam) ;
}


/******************************************************************/
/* Internal function : EnumCallback                               */
/******************************************************************/

BOOL _ProcListWnd_EnumCallback (void * pContext, PROCSTRUCT * pProc) 
{
  LVITEM	lvi = { 0 } ;
  HWND		hwndList = (HWND) pContext ;
  TCHAR		szBuffer[16] ;

  // verify params
  ASSERT (pContext!=NULL) ;
  ASSERT (pProc!=NULL) ;

  // set process name
  lvi.mask	= LVIF_TEXT | LVIF_PARAM ;
  lvi.iItem	= ListView_GetItemCount (hwndList) ;
  lvi.iSubItem	= COL_NAME ; 
  lvi.pszText	= pProc->szName ;
  lvi.lParam	= pProc->nProcessId ;
  ListView_InsertItem (hwndList, &lvi) ;

  // set PID
  wsprintf (szBuffer, TEXT("%u"), pProc->nProcessId) ;
  lvi.mask	= LVIF_TEXT ;
  lvi.iSubItem	= COL_PID ;
  lvi.pszText	= szBuffer ;
  ListView_SetItem (hwndList, &lvi) ;

  // set state
  lvi.mask	= LVIF_TEXT ;
  lvi.iSubItem	= COL_STATE ;
  if( pProc->nState==PS_HOOK_FAILED )
    lvi.pszText = (LPTSTR) STR_DEF (_HOOK_FAILED, TEXT("Not hooked (failed)")) ;
  else if( pProc->nState==PS_HOOK_DISABLED )
    lvi.pszText = (LPTSTR) STR_DEF (_HOOK_DISABLED, TEXT("Not hooked (disabled)")) ;
  else if( pProc->nState==PS_HOOKED_SINCE_BIRTH )
    lvi.pszText = (LPTSTR) STR_DEF (_HOOKED_SINCE_BIRTH, TEXT("Hooked (since birth)")) ;
  else if( pProc->nState==PS_HOOKED_WHILE_RUNNING )
    lvi.pszText = (LPTSTR) STR_DEF (_HOOKED_WHILE_RUNNING, TEXT("Hooked (while running)")) ;
  else if( pProc->nState==PS_HOOKED_BY_PREV_INSTANCE )
    lvi.pszText = (LPTSTR) STR_DEF (_HOOKED_BY_PREV_INSTANCE, TEXT("Hooked (by a previous instance)")) ;
  else
    lvi.pszText = (LPTSTR) STR_DEF (_UNKNOWN_STATE, TEXT("Unknown")) ;
  ListView_SetItem (hwndList, &lvi) ;

  // set path
  lvi.mask	= LVIF_TEXT ;
  lvi.iSubItem	= COL_PATH ;
  lvi.pszText	= pProc->szPath ;
  ListView_SetItem (hwndList, &lvi) ;
  
  return TRUE ;
}
